<?php
	/**
	  * @package	MyTools
	  * @name		JavaScriptWrapper
	  * @brief		Clase para generar clases javascript
	  * @author		Hermann D. Schimpf <hschimpf@gschimpf.com>
	  * @license	http://opensource.org/licenses/gpl-2.0.php
	  * @date		2010-01-28
	  * @version	0.1.0
  	**/

	final class JavaScriptWrapper extends MyDebuger implements MyVersions {
		/* version de la clase */
		private static $version			= 0;
		/* subversion de la clase */
		private static $subversion		= 1;
		/* numero de revision de la clase */
		private static $revision		= 0;

		/* clases generadas */
		private $classes	= Array();
		/* resultado generado */
		private $result	= False;

		public function __construct($debug = False) {
			/* creamos el padre */
			parent::__construct($debug, __CLASS__);
		}

		public function __destruct() {
			/* matamos el padre */
			parent::__destruct();
		}

		public static final function getVersion() {
			/* retornamos la version actual de la clase */
			return 'v' . self::$version . '.' . self::$subversion . '.' . self::$revision;
		}

		public function newClass($className = Null) {
			/* creamos la nueva clase */
			$this->catchExceptions('addNewClass', $className);
		}

		public function addAttr($className = Null, $attrName = Null, $value = Null) {
			/* agregamos el atributo a la clase */
			$this->catchExceptions('addNewAttr', $className, $attrName, $value);
		}

		public function addMethod($className = Null, $methodName = Null) {
			/* obtenemos los parametros para el metodo */
			$params = func_get_args();
			/* agregamos el metodo a la clase */
			eval($this->buildCommand($className, $methodName, $params));
		}

		public function addMethodLine($className = Null, $methodName = Null, $codeLine = Null) {
			/* agregamos la linea al metodo de la clase */
			$this->catchExceptions('addNewMethodLine', $className, $methodName, $codeLine);
		}

		public function getJavaScript() {
			/* procesamos el armado del javascript */
			$this->catchExceptions('buildJavaScript');
			/* retornamos el resultado */
			return '<SCRIPT languaje=\'javascript\'>' . $this->getResult() . '</SCRIPT>';
			//return '<code>' . $this->getResult() . '</code>';
		}

		private function addNewClass($className) {
			/* mostramos un mensaje */
			$this->showAddNewClass($className);
			/* realizamos unas verificaciones */
			$this->verificarNuevaClase($className);
			/* agregamos la clase */
			$this->addClass($className);
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function addNewAttr($className, $attrName, $value) {
			/* mostramos un mensaje */
			$this->showAddNewAttr($className, $attrName);
			/* realizamos unas verificaciones */
			$this->verificarNuevoAttr($className, $attrName);
			/* agregamos el nuevo metodo a la clase */
			$this->addClassAttr($className, $attrName, $value);
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function addNewMethod($className, $methodName) {
			/* mostramos un mensaje */
			$this->showAddNewMethod($className, $methodName);
			/* realizamos unas verificaciones */
			$this->verificarNuevoMetodo($className, $methodName);
			/* obtenemos los parametros del metodo */
			$params = func_get_args();
			/* agregamos el nuevo metodo a la clase */
			$this->addClassMethod($className, $methodName, $params);
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function addNewMethodLine($className, $methodName, $codeLine) {
			/* mostramos un mensaje */
			$this->showAddNewMethodLine($className, $methodName);
			/* realizamos unas verificaciones */
			$this->verificarNuevaLinea($className, $methodName, $codeLine);
			/* agregamos la nueva linea al metodo de la clase */
			$this->addClassMethodLine($className, $methodName, $codeLine);
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function buildJavaScript() {
			/* mostramos un mensaje */
			$this->show('Armando codigo javascript..');
			/* recorremos las clases */
			foreach ($this->getDefinedClasses() AS $className => $classData)
				/* procesamos la clase */
				$this->procesarClase($className, $classData);
		}

		private function procesarClase($className, $classData) {
			/* mostramos un mensaje */
			$this->show('Procesando la clase ' . $this->classFormat($className) . '..');
			/* agregamos la cabecera de la clase */
			$this->buildClassHead($className);
			/* agregamos los atributos de la clase */
			$this->buildClassAttrs($classData['attrs']);
			/* agregamos los metodos de la clase */
			$this->buildClassMethods($classData['methods']);
			/* finalizamos la definicion de la clase */
			$this->buildClassFooter($className);
		}

		private function buildClassHead($className) {
			/* mostramos un mensaje */
			$this->show('Agregando cabecera de la clase..', False);
			/* agregamos la definicion de la clase */
			$this->addResultLine('function ' . $className . '(){');
			/* agregamos la variable __CLASS__ */
			$this->addResultLine('var __CLASS__=\'' . $className . '\';');
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function buildClassAttrs($classAttrs) {
			/* mostramos un mensaje */
			$this->show('Agregando atributos de la clase..', False);
			/* recorremos los atributos */
			foreach ($classAttrs AS $attrName => $attrValue)
				/* agregamos el atributo */
				$this->addResultLine('this.' . $attrName . (
					/* verificamos si es nulo */
					$attrValue === Null ? '' :
					/* agregamos el valor del atributo */
					'=' . $this->getAttrValue($attrValue)
				/* agregamos el punto y coma final */
				) . ';');
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function buildClassMethods($classMethods) {
			/* mostramos un mensaje */
			$this->show('Agregando metodos de la clase..');
			/* recorremos los metodos */
			foreach ($classMethods AS $methodName => $methodData)
				/* agregamos el metodo */
				$this->procesarMetodo($methodName, $methodData);
		}

		private function procesarMetodo($methodName, $methodData) {
			/* mostramos un mensaje */
			$this->show('Agregando el metodo ' . $this->methodFormat($methodName) . ' de la clase..', False);
			/* agregamos la definicion del metodo */
			$this->buildMethodHead($methodName, $methodData['params']);
			/* agregamos las lineas del metodo */
			$this->buildMethodLines($methodData['lines']);
			/* finalizamos la definicion del metodo */
			$this->addResultLine('},');
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function buildMethodHead($methodName, $methodParams) {
			/* iniciamos la definicion del metodo */
			$this->addResultLine('this.' . $methodName . '=function(');
			/* recorremos los parametros del metodo */
			for ($i = 0; $i < count($methodParams); $i++)
				/* agregamos el parametro del metodo */
				$this->addResultLine($methodParams[$i] . (
				/* agregamos la coma de separacion entre parametros */
				$i < count($methodParams) - 1 ? ',' : ''));
			/* finalizamos la definicion del metodo */
			$this->addResultLine('){');
		}

		private function buildMethodLines($methodLines) {
			/* recorremos las lineas del metodo */
			foreach ($methodLines AS $codeLine)
				/* agregamos la linea del metodo */
				$this->addResultLine($codeLine);
		}

		private function buildClassFooter($className) {
			/* verificamos si se especifico un constructor */
			if ($this->methodExists($className, '__construct'))
				/* agregamos la llamada al constructor */
				$this->addResultLine('this.__construct();');
			/* finalizamos la definicion de la clase */
			$this->addResultLine('}');
		}

		private function getAttrValue($attrValue) {
			/* verificamos el tipo de valor */
			switch (strtolower(gettype($attrValue))) {
				/* si es un boolean retornamos True o False */
				case 'boolean': return ($attrValue ? 'true' : 'false');
				/* si es un entero lo retornamos como esta */
				case 'integer': case 'double': return $attrValue;
				/* retornamos entre comillas */
				default: return '\'' . addslashes($attrValue) . '\'';
			}
		}

		private function verificarNuevaClase($className, $exists = True) {
			/* verificamos si se especifico un nombre */
			if ($className === Null)
				/* salimos con una excepcion */
				$this->newException('No se especifico el nombre de la clase');
			/* verificamos si la clase ya existe */
			if ($this->classExists($className) === $exists)
				/* salimos con una excepcion */
				$this->newException('La clase ' . ($exists ? 'ya' : 'no') . ' esta definida');
		}

		private function verificarNuevoAttr($className, $attrName) {
			/* verificamos la clase */
			$this->verificarNuevaClase($className, False);
			/* verificamos si se especifico un nombre para el metodo */
			if ($attrName === Null)
				/* salimos con una excepcion */
				$this->newException('No se especifico el nombre para el atributo');
			/* verificamos si la clase ya tiene un metodo con ese nombre */
			if ($this->attrExists($className, $attrName))
				/* salimos con una excepcion */
				$this->newException('La clase ya tiene definido un atributo con el mismo nombre');
		}

		private function verificarNuevoMetodo($className, $methodName, $exists = True) {
			/* verificamos la clase */
			$this->verificarNuevaClase($className, False);
			/* verificamos si se especifico un nombre para el metodo */
			if ($methodName === Null)
				/* salimos con una excepcion */
				$this->newException('No se especifico el nombre para el metodo');
			/* verificamos si la clase ya tiene un metodo con ese nombre */
			if ($this->methodExists($className, $methodName) === $exists)
				/* salimos con una excepcion */
				$this->newException(
					/* verificamos el tipo de mensaje a mostrar */
					$exists === True ?
					/* mostramos el error */
					'La clase ya tiene definido un metodo con el mismo nombre' :
					/* mostramos el error */
					'La clase no tiene definido el metodo especificado'
				);
		}

		private function verificarNuevaLinea($className, $methodName, $codeLine) {
			/* verificamos la clase */
			$this->verificarNuevaClase($className, False);
			/* verificamos el metodo */
			$this->verificarNuevoMetodo($className, $methodName, False);
			/* verificamos si se especifico la linea de codigo */
			if ($codeLine === Null)
				/* salimos con una excepcion */
				$this->newException('No se especifico la linea de codigo para agregar al metodo');
		}

		private function showAddNewClass($className) {
			/* mostramos un mensaje */
			$this->show('Agregando la clase ' . (
				/* verificamos si especifico el atributo */
				$className === Null ? '' :
				/* mostramos el atrubuto */
				$this->classFormat(' ' . $className)
			/* finalizamos el mensaje */
			) . '..', False);
		}

		private function showAddNewAttr($className, $attrName) {
			/* mostramos un mensaje */
			$this->show('Agregando el atributo' . (
				/* verificamos si especifico el atributo */
				$attrName === Null ? '' :
				/* mostramos el atrubuto */
				$this->attrFormat(' ' . $attrName)
			/* agregamos la parte del mensaje */
			) . ' a la clase' . (
				/* verificamos si especifico el atributo */
				$className === Null ? '' :
				/* mostramos el atrubuto */
				$this->classFormat(' ' . $className)
			/* finalizamos el mensaje */
			) . '..', False);
		}

		private function showAddNewMethod($className, $methodName) {
			/* mostramos un mensaje */
			$this->show('Agregando el metodo' . (
				/* verificamos si especifico el atributo */
				$methodName === Null ? '' :
				/* mostramos el atrubuto */
				$this->methodFormat(' ' . $methodName)
			/* agregamos la parte del mensaje */
			) . ' a la clase' . (
				/* verificamos si especifico el atributo */
				$className === Null ? '' :
				/* mostramos el atrubuto */
				$this->classFormat(' ' . $className)
			/* finalizamos el mensaje */
			) . '..', False);
		}

		private function showAddNewMethodLine($className, $methodName) {
			/* mostramos un mensaje */
			$this->show('Agregando linea de codigo al metodo' . (
				/* verificamos si especifico el atributo */
				$methodName === Null ? '' :
				/* mostramos el atrubuto */
				$this->methodFormat(' ' . $methodName)
			/* agregamos la parte del mensaje */
			) . ' de la clase' . (
				/* verificamos si especifico el atributo */
				$className === Null ? '' :
				/* mostramos el atrubuto */
				$this->classFormat(' ' . $className)
			/* finalizamos el mensaje */
			) . '..', False);
		}

		private function classExists($className) {
			/* obtenemos las clases definidas */
			$classes = $this->getDefinedClasses();
			/* retornamos si la clase se definio */
			return isset($classes[$className]);
		}

		private function attrExists($className, $attrName) {
			/* obtenemos los atributos definidos en la clase */
			$attrs = $this->getClassAttrs($className);
			/* retornamos si el atributo existe en la clase */
			return isset($attrs[$attrName]);
		}

		private function methodExists($className, $methodName) {
			/* obtenemos los atributos definidos en la clase */
			$methods = $this->getClassMethods($className);
			/* retornamos si el metodo existe en la clase */
			return isset($methods[$methodName]);
		}

		private function getDefinedClasses() {
			/* ordenamos las clases */
			ksort($this->classes);
			/* retornamos las clases generadas */
			return $this->classes;
		}

		private function getClassAttrs($className) {
			/* ordenamos los atributos de la clase */
			ksort($this->classes[$className]['attrs']);
			/* retornamos los atributos de la clase */
			return $this->classes[$className]['attrs'];
		}

		private function getClassMethods($className) {
			/* ordenamos los metodos de la clase */
			ksort($this->classes[$className]['methods']);
			/* retornamos los metodos de la clase */
			return $this->classes[$className]['methods'];
		}

		private function getResult() {
			/* retornamos el resultado */
			return preg_replace('/(,})$/', '}', $this->result);
		}

		private function addClass($className) {
			/* agregamos el espacio para los atributos de la clase */
			$this->classes[$className]['attrs'] = Array();
			/* agregamos el espacio para los metodos de la clase */
			$this->classes[$className]['methods'] = Array();
		}

		private function addClassAttr($className, $attrName, $value) {
			/* agregamos el atributo a la clase */
			$this->classes[$className]['attrs'][$attrName] = $value;
		}

		private function addClassMethod($className, $methodName, $params) {
			/* creamos una variable de parametros */
			$methodParams = Array();
			/* armamos los parametros */
			for ($i = 2; $i < count($params); $i++)
				/* agregamos el parametro */
				$methodParams[] = $params[$i];
			/* agregamos el espacio para los parametros del metodo a la clase */
			$this->classes[$className]['methods'][$methodName]['params'] = $methodParams;
			/* agregamos el espacio para las lineas de codigo del metodo a la clase */
			$this->classes[$className]['methods'][$methodName]['lines'] = Array();
		}

		private function addClassMethodLine($className, $methodName, $codeLine) {
			/* agregamos la linea de codigo al metodo de la clase */
			$this->classes[$className]['methods'][$methodName]['lines'][] = $codeLine . (
				/* verificamos si no termina en punto y coma y lo agregamos */
				substr($codeLine, -1) === ';' ? '' : ';'
			);
		}

		private function addResultLine($line) {
			/* agregamos la linea al resultado */
			$this->result .= $line;
		}

		private function buildCommand($className, $methodName, $params) {
			/* armamos una cadena temporal */
			$string = '$this->catchExceptions(\'addNewMethod\', ' . (
				/* agregamos el nombre de la clase */
				$className === Null ? 'Null' : '\'' . $className . '\''
			) . ', ' . (
				/* agregamos el nombre del metodo */
				$methodName === Null ? 'Null' : '\'' . $methodName . '\''
			);
			/* recorremos los parametros */
			for ($i = 2; $i < count($params); $i++)
				/* agregamos el parametro */
				$string .= ($i < count($params) ? ',' : '') . ($params[$i] === Null ? 'Null' : '\'' . $params[$i] . '\'');
			/* retornamos la cadena */
			return $string . ');';
		}

		private function catchExceptions($method = Null) {
			/* ingresamos a un control de errores para capturar mis excepciones */
			try {
				/* ingresamos a un control de errores para capturar las excepciones de ejecucion */
				try {
					/* realizamos algunas verificaciones */
					$this->verificarMetodo($method);
					/* obtenemos los parametros recibidos */
					$args = func_get_args();
					/* llamamos al metodo enviando los parametros */
					eval('$this->' . $method . '(' . $this->buildArgs($args) . ');');
				/* capturamos las excepciones */
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->newException($e);
				}
			/* capturamos mis excepciones */
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}
	}
?>